package controller;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;

import mode.enumeration.Direction;
import model.command.RunnerCommand;
import model.listener.CheckPointListener;
import model.vo.CheckPoint;
import model.vo.Logging;
import model.vo.Runner;
import utils.NoLimeUtils;
import estimation.Estimation;

public class NLSimulator extends Thread {

	Runner runner;
	Long defaultSpeed = 1000l;
	double pixelSize = (long) 1.0;
	Direction lastDirection;
	double distance = 0;
	boolean isDebug = false;
	boolean isLoop = false;
	private final Collection<Logging> logger = new ArrayList<Logging>();

	public Collection<Logging> getLogger() {
		return logger;
	}

	Collection<RunnerCommand> commands = new ArrayList<RunnerCommand>();
	Collection<CheckPoint> checkpoints = new ArrayList<CheckPoint>();
	Collection<CheckPointListener> greedyListener = new ArrayList<CheckPointListener>();

	public NLSimulator(Runner runner) {
		this.runner = runner;
		this.addGreedyListener(new DefaultGreedyListener());
	}

	public NLSimulator(int x, int y) {
		this.runner = new Runner(x, y);
		this.addGreedyListener(new DefaultGreedyListener());
	}

	public Long getDefaultSpeed() {
		return defaultSpeed;
	}

	public void setDefaultSpeed(Long defaultSpeed) {
		this.defaultSpeed = defaultSpeed;
	}

	public void run() {
		for (RunnerCommand position : commands) {

			lastDirection = position.getDirection();

			if (position.getDirection() == Direction.HORIZONTAL) {

				int amount = Math.abs(position.getAmount());

				int directionFixer = position.getAmount() < 0 ? -1 : 1;

				do {

					// fireGreedyListener();
					fireCheckPoint();

					amount--;
					distance+=pixelSize;
					runner.setDistance(Long.valueOf(runner.getDistance()
							.longValue()
							+ ((long) pixelSize)));
					runner.getPosition().setX(
							runner.getPosition().getX() + pixelSize
									* directionFixer);

					if (isDebug)
						System.out.println("x:" + runner.getPosition().getX()
								+ "/" + runner.getPosition().getY()
								+ " - time "
								+ NoLimeUtils.formatTime(new Date().getTime()));
					try {
						Thread.sleep(defaultSpeed);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}

					// fireCheckPoint();

				} while (amount > 0);

			} else {

				int amount = Math.abs(position.getAmount());

				int directionFixer = position.getAmount() < 0 ? -1 : 1;

				do {
					// fireGreedyListener();
					fireCheckPoint();

					amount--;
					distance+=pixelSize;
					runner.getPosition().setY(
							runner.getPosition().getY() + pixelSize
									* directionFixer);

					if (isDebug)
						System.out.println("x:" + runner.getPosition().getX()
								+ "/" + runner.getPosition().getY()
								+ " - time:"
								+ NoLimeUtils.formatTime(new Date().getTime()));
					try {
						Thread.sleep(defaultSpeed);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}

					// fireCheckPoint();

				} while (amount > 0);

			}

		}

		if (isLoop) {
			Thread thread = new Thread(this);
			thread.start();
		}

	}

	private void fireCheckPoint() {
		for (CheckPoint checkpoint : checkpoints) {

			if (runner.getPosition().equals(checkpoint.getPosition())) {
				fireGreedyListener();
				checkpoint.getListener().reached(this);
				runner.setDistance(Long.valueOf(0));
				// break;
			}

		}
	}

	private void fireGreedyListener() {
		for (CheckPointListener checkpoint : greedyListener) {
			checkpoint.reached(this);
			// break;
		}
	}

	public void addCommand(RunnerCommand rc) {

		commands.add(rc);
	}

	public void addCommand(Direction dir, int amount) {

		commands.add(new RunnerCommand(dir, amount));

	}

	public void addCheckPoint(CheckPoint cp) {
		checkpoints.add(cp);
	}

	public void addCheckPoint(int x, int y, CheckPointListener cp) {
		this.addCheckPoint(new CheckPoint(x, y, cp));

	}

	public void addGreedyListener(CheckPointListener cp) {
		greedyListener.add(cp);
	}

	public Runner getRunner() {
		return runner;
	}

	public void setRunner(Runner runner) {
		this.runner = runner;
	}

	public double getPixelSize() {
		return pixelSize;
	}

	public void setPixelSize(double pixelSize) {
		this.pixelSize = pixelSize;
	}

	public double Predict(Class method, double journey) {

		try {
			Estimation e = Estimation.class.cast(method.newInstance());
			e.setSampleCollection(this.logger);
			return e.getPrediction(journey);
		} catch (InstantiationException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		} catch (IllegalAccessException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}

		return -1;

	}

	public boolean isDebug() {
		return isDebug;
	}

	public void setDebug(boolean isDebug) {
		this.isDebug = isDebug;
	}

	public Collection<CheckPoint> getCheckpoints() {
		return checkpoints;
	}

	public boolean isLoop() {
		return isLoop;
	}

	public void setLoop(boolean isLoop) {
		this.isLoop = isLoop;
	}

	public void addLogger(Logging model) {
		// System.out.println("Adicionou!!");
		this.logger.add(model);
	}

}

class DefaultGreedyListener implements CheckPointListener {

	public void reached(NLSimulator m) {
		//System.out.println("fire..");

		if (m.getLogger().size() != 0) {

			Logging log = ((List<Logging>) m.getLogger()).get(m.getLogger()
					.size() - 1);

			if (NoLimeUtils.subtract(log.getPosition(), m.getRunner()
					.getPosition()) == 0) {
				return;
			}

		}

		m.addLogger(new Logging(m.distance, m.getRunner().getPosition()
						.clone(), System.currentTimeMillis()));
	}
}
